package com.lovely3x.jobservice.executors.extensions.downloader2.executor;

import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils;

import com.lovely3x.common.utils.ALog;
import com.lovely3x.jobservice.executors.extensions.downloader2.DownloadTask;
import com.lovely3x.jobservice.executors.extensions.downloader2.exception.CancelException;
import com.lovely3x.jobservice.executors.extensions.downloader2.exception.DiskInsufficientException;
import com.lovely3x.jobservice.executors.extensions.downloader2.exception.PartDownloadFailureException;
import com.lovely3x.jobservice.executors.extensions.downloader2.serializer.RandomAccessFile;
import com.lovely3x.jobservice.executors.extensions.downloader2.serializer.RandomAccessWriter;
import com.lovely3x.jobservice.executors.extensions.downloader2.utils.DownloadUtils;
import com.lovely3x.jobservice.task.Progress;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * 下载实现者
 * Created by lovely3x on 16-1-28.
 */
public class Downloader2 {
    public static final String TAG = "Downloader2";
    final List<Part> mParts = new ArrayList<>();
    /**
     * 当前的这个任务的下载线程
     */
    private final List<DownloadThread> DOWNLOAD_THREADS = new ArrayList<>();
    /**
     * 文件锁
     */
    private final Object FILE_WRITE_LOCK = new Object();
    /**
     * 是否停止
     */
    private final AtomicBoolean mStopped = new AtomicBoolean(false);
    private DownloadProgressListener mDownloadProgressListener;
    private DownloadTask mTask;
    private RandomAccessFile mRandomWriter;
    private Progress mProgress;
    private DownloadProgressMerger mMerger;

    /**
     * 开始下载
     *
     * @param task 需要下载的任务对象
     *             //* @param parts 已经下载的块的描述
     */
    public void start(DownloadTask task, Progress progress, List<Part> parts) throws Exception {
        this.mTask = task;
        this.mProgress = progress;
        this.mProgress.setState(Progress.State.EXECUTING);
        this.mProgress.setTask(task.what);
        this.mParts.clear();
        if (parts != null && !parts.isEmpty()) {
            resume(parts);
        } else {
            try {
                mTask.setDownloadParts(mParts);
                if (mDownloadProgressListener != null) mDownloadProgressListener.onStart(task.what);

                this.mMerger = new DownloadProgressMerger(mProgress);
                this.mMerger.setDownloadProgressListener(mDownloadProgressListener);

                //提取需要下载的请求的信息
                String url = task.downloadUrl;
                String file = task.storePath;
                int downloadThreadNum = task.downloadThread <= 0 ? DownloadTaskExecutor.SINGLE_DOWNLOAD_THREAD : task.downloadThread;
                String requestMethod = TextUtils.isEmpty(task.requestMethod) ? DownloadUtils.REQUEST_METHOD_POST : task.requestMethod;

                DownloadUtils.ResponseInfo fileInfo = DownloadUtils.getFileInfo(url);
                if (fileInfo == null) throw new NullPointerException("无法获取网络文件信息");

                long contentLength = fileInfo.contentLength;
                this.mProgress.setMax(contentLength);

                if (contentLength <= 0) {
                    downloadThreadNum = DownloadTaskExecutor.SINGLE_DOWNLOAD_THREAD;
                    contentLength = -1;
                }
                if (downloadThreadNum > DownloadTaskExecutor.MAX_DOWNLOAD_THREAD)
                    downloadThreadNum = DownloadTaskExecutor.MAX_DOWNLOAD_THREAD;

                long blockSize = contentLength / downloadThreadNum;

                File f = null;
                if (!TextUtils.isEmpty(file)) {
                    f = new File(file);
                } else {
                    throw new NullPointerException("未指定文件保存路径");
                }
                if (f.exists()) if (!f.delete()) throw new IllegalStateException("无法删除目标文件");
                if (!f.createNewFile()) throw new IllegalStateException("无法创建文件");

                if (f.getFreeSpace() < contentLength)
                    throw new DiskInsufficientException(String.format("磁盘空间不足 需要的磁盘空间至少为 %d 而目前磁盘的空间仅有 %d ", contentLength, f.getFreeSpace()));

                mRandomWriter = new RandomAccessFile(f, "rw");
                //无法获取内容长度
                if (contentLength == -1) {
                    Part part = new Part(Progress.State.EXECUTING, task.what, url, file, 0, 0, -1, 1);
                    mParts.add(part);
                    DownloadThread downloadThread = new DownloadThread(part, requestMethod, mRandomWriter, mMerger);
                    DOWNLOAD_THREADS.add(downloadThread);
                    downloadThread.start();
                } else {
                    //分发
                    for (int i = 0; i < downloadThreadNum; i++) {
                        long start = i * blockSize;
                        long end = (i + 1) * blockSize;
                        if (i + 1 == downloadThreadNum) end = contentLength;
                        Part part = new Part(Progress.State.EXECUTING, task.what, url, file, start, 0, end - 1, i + 1);
                        mParts.add(part);
                        DownloadThread downloadThread = new DownloadThread(part, requestMethod, mRandomWriter, mMerger);
                        DOWNLOAD_THREADS.add(downloadThread);
                        downloadThread.start();
                    }
                }

            } catch (Exception e) {
                throw new Exception(e);
            }
        }
    }

    /**
     * 使用指定的块；列表恢复任务
     *
     * @param parts 块列表
     */
    public void resume(List<Part> parts) throws Exception {
        try {
            if (mDownloadProgressListener != null) mDownloadProgressListener.onStart(mTask.what);

            this.mMerger = new DownloadProgressMerger(mProgress);
            this.mMerger.setDownloadProgressListener(mDownloadProgressListener);

            //提取需要下载的请求的信息
            String url = mTask.downloadUrl;
            String requestMethod = TextUtils.isEmpty(mTask.requestMethod) ? DownloadUtils.REQUEST_METHOD_POST : mTask.requestMethod;

            DownloadUtils.ResponseInfo fileInfo = DownloadUtils.getFileInfo(url);
            if (fileInfo == null) throw new NullPointerException("无法获取网络文件信息");

            long contentLength = fileInfo.contentLength;
            this.mProgress.setMax(contentLength);

            if (contentLength <= 0) {
                contentLength = -1;
            }


            //文件是否发生了变化
            //如果文件发生了变化，需要重新下载
            if (contentLength != mProgress.getMax()) {
                throw new IllegalStateException("文件已经发生变化，无法继续下载");
            }

            File f = new File(mTask.storePath);
            if (!f.exists()) {
                throw new FileNotFoundException("已经下载的文件被删除。");
            }

            if (f.getFreeSpace() < contentLength)
                throw new DiskInsufficientException(String.format("磁盘空间不足 需要的磁盘空间至少为 %d 而目前磁盘的空间仅有 %d ", contentLength, f.getFreeSpace()));

            mRandomWriter = new RandomAccessFile(f, "rw");
            //无法获取内容长度
            if (contentLength == -1) {
                Part part = parts.get(0);
                mParts.add(part);
                DownloadThread downloadThread = new DownloadThread(part, requestMethod, mRandomWriter, mMerger);
                DOWNLOAD_THREADS.add(downloadThread);
                downloadThread.start();
            } else {
                for (Part part : parts) {
                    mParts.add(part);
                    DownloadThread downloadThread = new DownloadThread(part, requestMethod, mRandomWriter, mMerger);
                    DOWNLOAD_THREADS.add(downloadThread);
                    downloadThread.start();
                }
            }

        } catch (Exception e) {
            throw new Exception(e);
        }
    }


    /**
     * 停止下载
     */
    public void stop() {
        synchronized (mStopped) {
            this.mStopped.set(true);
        }
        synchronized (DOWNLOAD_THREADS) {
            for (DownloadThread dt : DOWNLOAD_THREADS) {
                try {
                    dt.interrupt();
                } catch (Exception ignored) {
                }
            }
        }
    }

    /**
     * 是否已经停止
     *
     * @return true or false
     */
    public boolean isStop() {
        synchronized (mStopped) {
            return mStopped.get();
        }
    }

    /**
     * 设置下载进度监听器
     *
     * @param listener 监听器对象可以为
     */
    public void setDownloadProgressListener(@Nullable DownloadProgressListener listener) {
        this.mDownloadProgressListener = listener;
        if (mMerger != null && mMerger.mDownloadProgressListener == null) {
            mMerger.setDownloadProgressListener(mDownloadProgressListener);

        }
    }

    /**
     * 下载进度监听器
     */
    public interface DownloadProgressListener {

        /**
         * 开始下载
         *
         * @param taskWhat 开始下载的任务
         */
        void onStart(long taskWhat);

        /**
         * 下载中
         *
         * @param taskWhat 下载的任务
         * @param current  当前已经下载了多少
         * @param max      该文件的最大值 可能为-1,表示无法计算
         */
        void onDoing(long taskWhat, long current, long max);

        /**
         * 当下载失败后执行
         *
         * @param taskWhat 下载失败的任务
         * @param e        失败的原因
         */
        void onFailure(long taskWhat, Exception e);

        /**
         * 当下载成功后执行
         *
         * @param taskWhat      下载成功的任务
         * @param downloadBytes 下载的字节数量
         */
        void onSuccessful(long taskWhat, long downloadBytes);
    }

    /**
     * 下载线程
     */
    public class DownloadThread extends Thread implements DownloadListener {
        private static final String TAG = "DownloadThread";
        private final Part mPart;
        private final String mRequestMethod;
        private final DownloadListener mDownloadListener;
        private RandomAccessWriter mFile;

        /**
         * 需要下载的块对象
         *
         * @param part 需要下载的块
         */
        public DownloadThread(Part part, String requestMethod, RandomAccessWriter randomAccess, @NonNull DownloadListener listener) {
            super(String.format("thread-downloader-%d-%d", part.taskWhat, part.sequence));
            this.mPart = part;
            this.mRequestMethod = requestMethod;
            this.mDownloadListener = listener;
            mFile = randomAccess;
            ALog.i(TAG, String.format("use thread %s prepare download start %d current %d  end %d", String.format("thread-downloader-%d-%d", part.taskWhat, part.sequence), part.start, part.current, part.end));
        }

        @Override
        public void run() {
            DownloadUtils.downloadPart(mPart.url, mRequestMethod, (mPart.current > mPart.start ? mPart.current - mPart.start : 0), mPart.start, mPart.end, mPart.taskWhat, mPart.sequence, this);
            synchronized (DOWNLOAD_THREADS) {
                //操作完成,移除自己
                DOWNLOAD_THREADS.remove(this);
            }
        }

        @Override
        public void onStart(long taskWhat, int sequence) {
            mDownloadListener.onStart(taskWhat, sequence);
        }

        @Override
        public void onDoing(long start, long max, long current, long taskWhat, int partSequence, byte[] buf, int len) {
            if (!isStop() && !interrupted()) {
                mPart.state = Progress.State.EXECUTING;
                mPart.current = start + current;
                mPart.start = start;
                mPart.end = max;
                try {
                    synchronized (FILE_WRITE_LOCK) {
                        mFile.seek(start + (current - len));
                        mFile.write(buf, 0, len);
                    }
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
            mDownloadListener.onDoing(start, max, current, taskWhat, partSequence, buf, len);
        }

        @Override
        public void onFailure(Exception e, long start, long max, long current, long taskWhat, int partSequence) {
            e.printStackTrace();
            mPart.state = Progress.State.FAILURE;
            mPart.current = start + current;
            mPart.start = start;
            mPart.end = max;
            mDownloadListener.onFailure(e, start, max, current, taskWhat, partSequence);
        }

        @Override
        public void onSuccessful(long start, long max, long current, long taskWhat, int partSequence) {
            mPart.state = Progress.State.SUCCESSFUL;
            mPart.current = start + current;
            mPart.start = start;
            mPart.end = max;
            mDownloadListener.onSuccessful(start, max, current, taskWhat, partSequence);
        }

        @Override
        public void onCancel(long start, long max, long current, long taskWhat, int partSequence) {
            mPart.state = Progress.State.FAILURE;
            mPart.current = start + current;
            mPart.start = start;
            mPart.end = max;
            mDownloadListener.onCancel(start, max, current, taskWhat, partSequence);
        }

        @Override
        public boolean cancel() {
            boolean interrupted = isStop() || isInterrupted();
            if (interrupted) return true;
            if (mDownloadListener.cancel()) {
                throw new PartDownloadFailureException();
            }
            return false;
        }
    }

    /**
     * 下载进度合并器
     */
    public class DownloadProgressMerger implements DownloadListener {

        private final Object CALL_BACK_LOCK = new Object();
        private final Progress mProgress;

        /**
         * 是否有某一个块失败了
         */
        private final AtomicBoolean SOMEONE_IS_FAILURE = new AtomicBoolean(false);
        private DownloadProgressListener mDownloadProgressListener;

        public DownloadProgressMerger(Progress progress) {
            this.mProgress = progress;
        }


        /**
         * 设置下载进度监听器
         *
         * @param listener 监听器对象可以为
         */
        public void setDownloadProgressListener(@Nullable DownloadProgressListener listener) {
            this.mDownloadProgressListener = listener;
        }

        @Override
        public void onStart(long taskWhat, int sequence) {

        }

        @Override
        public void onDoing(long start, long max, long current, long taskWhat, int partSequence, byte[] buf, int len) {
            synchronized (mParts) {
                long downloaded = 0;
                for (Part part : mParts) downloaded += (part.current - part.start);
                synchronized (mProgress) {
                    mProgress.setCurrent(downloaded);
                }
                synchronized (CALL_BACK_LOCK) {
                    if (mDownloadProgressListener != null)
                        mDownloadProgressListener.onDoing(taskWhat, mProgress.getCurrent(), mProgress.getMax());
                }
            }
        }

        @Override
        public void onFailure(Exception e, long start, long max, long current, long taskWhat, int partSequence) {
            synchronized (SOMEONE_IS_FAILURE) {
                SOMEONE_IS_FAILURE.set(true);
            }
            synchronized (CALL_BACK_LOCK) {
                if (mDownloadProgressListener != null)
                    mDownloadProgressListener.onFailure(taskWhat, e);
            }
        }

        @Override
        public void onSuccessful(long start, long max, long current, long taskWhat, int partSequence) {
            synchronized (mParts) {
                long downloadedBytes = 0;

                for (Part part : mParts) {
                    if (part.state != Progress.State.SUCCESSFUL) return;
                    downloadedBytes += (part.current - part.start);
                }

                synchronized (CALL_BACK_LOCK) {
                    if (mDownloadProgressListener != null)
                        mDownloadProgressListener.onSuccessful(taskWhat, downloadedBytes);
                }
            }
        }

        @Override
        public void onCancel(long start, long max, long current, long taskWhat, int partSequence) {
            synchronized (CALL_BACK_LOCK) {
                if (mDownloadProgressListener != null)
                    mDownloadProgressListener.onFailure(taskWhat, new CancelException());
            }
        }

        @Override
        public boolean cancel() {
            synchronized (SOMEONE_IS_FAILURE) {
                return SOMEONE_IS_FAILURE.get();
            }
        }
    }
}
